/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ai;

import java.io.Serializable;
import java.util.Random;

/**
 *
 * @author stoen
 */
public class ANN implements AI_Interface, Serializable {
	private String name = "ANN";
    // Number of nodes in the hidden layer
    private final int nrOfNodes = 40;
    // Number of inputs: 4 for each of the 24 points + 1 for the bar = 25*4 for
    // each player, giving 100*2 = 200 input switches.
    /*
     * input[0 - 95] Black(AI) pieces
     * input[96 - 191] White(opponent) pieces
     * input[192-195] Black on bar
     * input[196-199] White on bar
     * input[200] if race
     */
    private final int nrOfInputs = 201;

    // Input switches: 0-119 = white pieces on points (24*5), 120-124 white
    // pieces on bar, 125-244 = black pieces on points, 245-299 = black on bar
    private boolean[] input;
    // For each input, a weight for each hidden layer node
    private double[][] inputWeights;
    // For each hidden layer node, a weight for the output
    private double[] outputWeights;

    private double[] hiddenLayer;
    
    private double[] thresholds;

    private int nrOfAdjusts, nrOfGamesPlayed;
    boolean player = false;
    Random r = new Random();
    
    public ANN () {
        inputWeights = new double[nrOfNodes][nrOfInputs];
        outputWeights = new double[nrOfNodes];
        hiddenLayer = new double[nrOfNodes];
        thresholds = new double[nrOfNodes];
        Random r = new Random();

        /*for (int i = 0; i < thresholds.length; i++) {
            thresholds[i] = r.nextFloat()*30;
        }*/
    }
    
    public void randomWeights(){
        for (int i = 0; i < inputWeights.length; i++) {
        	for(int j = 0; j < nrOfInputs;j++)
            inputWeights[i][j] = r.nextGaussian(); 
        }

        for (int i = 0; i < outputWeights.length; i++) {
            outputWeights[i] = r.nextGaussian();
        }
    }

    public double evaluate(byte[] board, boolean isRace) {
        double output = 0;
        hiddenLayer = new double[nrOfNodes];

        initializeInputs(board, isRace);

        for (int i = 0; i < nrOfNodes; i++) {
        	for (int j = 0; j < nrOfInputs; j++) {
                if (input[i] == true) 
                    hiddenLayer[i] += inputWeights[i][j];
        	}
        }


        for (int i = 0; i < nrOfNodes; i++) {
                output += outputWeights[i]*sigmoid(hiddenLayer[i]);
        }

        return (float)output;
    }

    private double sigmoid(double z){
    	return (1/(1 + Math.exp(-1*z)));
    }
    
    public void initializeInputs(byte[] board, boolean isRace) {
        // Number of inputs: 4 for each of the 24 points + 1 for the bar = 25*4 for
        // each player, giving 100*2 = 250 input switches.
        /*
         * input[0 - 95] Black(AI) pieces
         * input[96 - 191] White(opponent) pieces
         * input[192-195] Black on bar
         * input[196-199] White on bar
         * input[200] if race
         */
        
        input = new boolean[nrOfInputs]; // default values: false

        for (int i = 1; i <= 24; i++) {

        	if(board[i] == 0){
        		continue;
        	}
        	
        	if (board[i] > 0) { //position has a black piece
                for (int j = 0; j < 4; j++) {
                    if (board[i] > j) {
                        input[(i-1)*4 + j] = true;
                    }
                }
            } 
            else { //position has a white piece
                for (int j = 0; j < 4; j++) {
                    if (Math.abs(board[i]) > j) {
                        input[96 + (i-1)*4 + j] = true;
                    }
                }
            }
        }
        //Black bar
        if (board[25] != 0) {
            for (int i = 0; i < 4; i++) {
                if (board[25] > i) {
                    input[192 + i] = true;
                }
            }
        }
        //White Bar
        if (board[0] != 0) {
            for (int i = 0; i < 5; i++) {
                if (Math.abs(board[0]) > i) {
                    input[196 + i] = true;
                }
            }
        }
        //race condition
        input[200] = isRace;
    }

   /* public void adjust(float[] inWeights, float[] outWeights, float[] thresholds) {
        incrementAdjusts();
        for (int i = 0; i < inWeights.length; i++) {
 //           this.inputWeights[i] += (inWeights[i] - this.inputWeights[i]) * 0.05f;
        }

        for (int i = 0; i < outWeights.length; i++) {
            this.outputWieghts[i] += (outWeights[i] - this.outputWieghts[i]) * 0.05f;
        }

        for (int i = 0; i < thresholds.length; i++) {
            this.thresholds[i] += (thresholds[i] - this.thresholds[i]) * 0.05f;
        }
    }*/
    public void adjust(ANN challenger){
    	double[][] ChallengerInWeights = challenger.getInWeights();
    	double[] ChallengerOutWeights = challenger.getOutWeights();
    	//adjusting input weights
    	for(int node = 0; node < nrOfNodes;node++){
    		for(int input = 0; input < nrOfInputs;input++){
    			inputWeights[node][input] = 0.95*inputWeights[node][input]+0.05*ChallengerInWeights[node][input];
    		}
    	}
    	//adjusting outputweights
    	for (int node = 0; node < outputWeights.length; node++){
    		outputWeights[node] = 0.95*outputWeights[node]+0.05*ChallengerOutWeights[node];
    	}
    }
    
    /**
     * Spawns a new challanger based on this ANN
     * @return
     */
    public ANN mutate(){
        Random r = new Random();
    	ANN mutant = new ANN();
    	double[][] tmp = getInWeights();
    	double[][] newInW = new double[nrOfNodes][nrOfInputs];
    	double[] newOutW = getOutWeights().clone();
    	
    	for(int node = 0; node < nrOfNodes;node++){
    		for(int input = 0; input < nrOfInputs;input++){
    			newInW[node][input] = tmp[node][input];
    			newInW[node][input] = newInW[node][input]+0.10*r.nextGaussian();
    		}
    	}
    	for(int node = 0; node < nrOfNodes;node++){
    		newOutW[node] = newOutW[node]+0.10*r.nextGaussian();
    	}
    	
    	mutant.setWeights(newInW, newOutW);
    	
    	return mutant;
    	
    }
    
    

    public double[][] getInWeights() {
        return inputWeights;
    }

    public double[] getOutWeights() {
        return outputWeights;
    }

    public double[] getThresholds() {
        return thresholds;
    }
    
    public void setWeights(double[][] inputWeights, double[] outputWeights){
    	this.inputWeights = inputWeights;
    	this.outputWeights = outputWeights;
    }

   /* public void printInWeights(){
        System.out.println();
        for (double f : inputWeights)
            System.out.print(f + " ");
        System.out.println();
    }*/

   /* public void printOutWeights(){
        System.out.println();
        for (float f : outputWieghts)
            System.out.print(f + " ");
        System.out.println();
    }*/

    public void incrementGamesPlayed() {
        nrOfGamesPlayed++;
    }

    public void incrementAdjusts() {
        nrOfAdjusts++;
    }

    public int getNrOfAdjusts() {
        return nrOfAdjusts;
    }

    public int getNrOfGamesPlayed() {
        return nrOfGamesPlayed;
    }

    public void setPlayer(boolean player) {
        this.player = player;
    }
    public String whatsMahName(){
    	return name;
    }
}
